package org.force4spring;

import java.rmi.RemoteException;
import java.util.Calendar;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.force4spring.support.ForceSession;
import org.force4spring.support.SObjectDynaBean;
import org.force4spring.support.SessionFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;

import com.sforce.soap.partner.DeleteResult;
import com.sforce.soap.partner.DescribeGlobalResult;
import com.sforce.soap.partner.DescribeLayoutResult;
import com.sforce.soap.partner.DescribeSObjectResult;
import com.sforce.soap.partner.DescribeSoftphoneLayoutResult;
import com.sforce.soap.partner.DescribeTabSetResult;
import com.sforce.soap.partner.Email;
import com.sforce.soap.partner.GetDeletedResult;
import com.sforce.soap.partner.GetServerTimestampResult;
import com.sforce.soap.partner.GetUpdatedResult;
import com.sforce.soap.partner.GetUserInfoResult;
import com.sforce.soap.partner.LeadConvert;
import com.sforce.soap.partner.LeadConvertResult;
import com.sforce.soap.partner.MergeRequest;
import com.sforce.soap.partner.MergeResult;
import com.sforce.soap.partner.ProcessRequest;
import com.sforce.soap.partner.ProcessResult;
import com.sforce.soap.partner.ResetPasswordResult;
import com.sforce.soap.partner.SaveResult;
import com.sforce.soap.partner.SearchResult;
import com.sforce.soap.partner.SendEmailResult;
import com.sforce.soap.partner.SetPasswordResult;
import com.sforce.soap.partner.UndeleteResult;
import com.sforce.soap.partner.UpsertResult;
import com.sforce.soap.partner.fault.ApiFault;

public class ForceTemplate extends ForceAccessor {
	private static final Log log = LogFactory.getLog(ForceTemplate.class);
	
	private boolean allowCreate = true;

	private boolean alwaysUseNewSession = false;
	
	public ForceTemplate() {
	}
	
	public ForceTemplate(SessionFactory sessionFactory) {
		setSessionFactory(sessionFactory);
	}

	/**
	 * @return the allowCreate
	 */
	public boolean isAllowCreate() {
		return allowCreate;
	}

	/**
	 * @param allowCreate
	 *            the allowCreate to set
	 */
	public void setAllowCreate(boolean allowCreate) {
		this.allowCreate = allowCreate;
	}

	/**
	 * @return the alwaysUseNewSession
	 */
	public boolean isAlwaysUseNewSession() {
		return alwaysUseNewSession;
	}

	/**
	 * @param alwaysUseNewSession
	 *            the alwaysUseNewSession to set
	 */
	public void setAlwaysUseNewSession(boolean alwaysUseNewSession) {
		this.alwaysUseNewSession = alwaysUseNewSession;
	}

	/**
	 * Return a Session for use by this template.
	 * <p>
	 * Returns a new Session in case of "alwaysUseNewSession" (using the same
	 * JDBC Connection as a transactional Session, if applicable), a pre-bound
	 * Session in case of "allowCreate" turned off, and a pre-bound or new
	 * Session else (new only if no transactional or otherwise pre-bound Session
	 * exists).
	 * 
	 * @return the Session to use (never <code>null</code>)
	 * @see SessionFactoryUtils#getSession
	 * @see SessionFactoryUtils#getNewSession
	 * @see #setAlwaysUseNewSession
	 * @see #setAllowCreate
	 */
	protected ForceSession getSession() {
		if (isAlwaysUseNewSession()) {
			return SessionFactoryUtils.getNewSession(getSessionFactory());
		} else if (!isAllowCreate()) {
			return SessionFactoryUtils.getSession(getSessionFactory(), false);
		} else {
			return SessionFactoryUtils.getSession(getSessionFactory(), true);
		}
	}

	/**
	 * Execute the action specified by the given action object within a Session.
	 * 
	 * @param action
	 *            callback object that specifies the Apex action
	 * @return a result object returned by the action, or <code>null</code>
	 * @throws org.springframework.dao.DataAccessException
	 *             in case of Apex errors
	 */
	public Object execute(ForceCallback action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");
		ForceSession session = getSession();
		
		boolean existingTransaction = SessionFactoryUtils
				.isSessionTransactional(session, getSessionFactory());
		if (existingTransaction) {
			log.debug("Found thread-bound Session for ForceTemplate");
		}
		try {
			Object result = action.doInForce(session);
			flushIfNecessary(session, existingTransaction);
			return result;
		} catch (ApiFault fault) {
			throw convertApiFault(fault);
		} catch (RemoteException re) {
			throw new WebServicesCallException(re);
		} catch (RuntimeException ex) {
			// Callback code threw application exception...
			throw ex;
		} finally {
			if (existingTransaction) {
				log
						.debug("Not closing pre-bound Apex Session after ApexTemplate");
			} else {
				if (isAlwaysUseNewSession()) {
					SessionFactoryUtils.closeSession(session);
				}
				else {
					SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
				}
			}
		}
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#convertLead(com.sforce.soap.partner.LeadConvert[])
	 */
	public LeadConvertResult[] convertLead(final LeadConvert[] leadConverts)
			throws DataAccessException {
		return (LeadConvertResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.convertLead(leadConverts);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#create(com.sforce.soap.partner.sobject.SObject[])
	 */
	public SaveResult[] create(final SObjectDynaBean[] objects) throws DataAccessException {
		return (SaveResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.create(objects);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#delete(java.lang.String[])
	 */
	public DeleteResult[] delete(final String[] ids) throws DataAccessException {
		return (DeleteResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.delete(ids);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#describeGlobal()
	 */
	public DescribeGlobalResult describeGlobal() throws DataAccessException {
		return (DescribeGlobalResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.describeGlobal();
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#describeLayout(java.lang.String,
	 *      java.lang.String[])
	 */
	public DescribeLayoutResult describeLayout(final String type,
			final String[] recordTypeIds) throws DataAccessException {
		return (DescribeLayoutResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.describeLayout(type,recordTypeIds);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#describeSObject(java.lang.String)
	 */
	public DescribeSObjectResult describeSObject(final String type)
			throws DataAccessException {
		return (DescribeSObjectResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.describeSObject(type);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#describeSObjects(java.lang.String[])
	 */
	public DescribeSObjectResult[] describeSObjects(final String[] types)
			throws DataAccessException {
		return (DescribeSObjectResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.describeSObjects(types);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#describeSoftphoneLayout()
	 */
	public DescribeSoftphoneLayoutResult describeSoftphoneLayout()
			throws DataAccessException {
		return (DescribeSoftphoneLayoutResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.describeSoftphoneLayout();
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#describeTabs()
	 */
	public DescribeTabSetResult[] describeTabs() throws DataAccessException {
		return (DescribeTabSetResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.describeTabs();
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#getDeleted(java.lang.String,
	 *      java.util.Calendar, java.util.Calendar)
	 */
	public GetDeletedResult getDeleted(final String type, final Calendar start, final Calendar end)
			throws DataAccessException {
		return (GetDeletedResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.getDeleted(type,start,end);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#getServerTimestamp()
	 */
	public GetServerTimestampResult getServerTimestamp()
			throws DataAccessException {
		return (GetServerTimestampResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.getServerTimestamp();
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#getUpdated(java.lang.String,
	 *      java.util.Calendar, java.util.Calendar)
	 */
	public GetUpdatedResult getUpdated(final String type, final Calendar start, final Calendar end)
			throws DataAccessException {
		return (GetUpdatedResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.getUpdated(type,start,end);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#getUserInfo()
	 */
	public GetUserInfoResult getUserInfo() throws DataAccessException {
		return (GetUserInfoResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.getUserInfo();
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#merge(com.sforce.soap.partner.MergeRequest[])
	 */
	public MergeResult[] merge(final MergeRequest[] mergeRequests)
			throws DataAccessException {
		return (MergeResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.merge(mergeRequests);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#process(com.sforce.soap.partner.ProcessRequest[])
	 */
	public ProcessResult[] process(final ProcessRequest[] processRequests)
			throws DataAccessException {
		return (ProcessResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.process(processRequests);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#query(java.lang.String)
	 */
	public List query(final String soql) throws DataAccessException {
		return (List)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.query(soql);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#queryAll(java.lang.String)
	 */
	public List queryAll(final String soql) throws DataAccessException {
		return (List)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.queryAll(soql);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#queryMore(java.lang.String)
	 */
	public List queryMore(final String queryLocator) throws DataAccessException {
		return (List)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.queryMore(queryLocator);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#resetPassword(java.lang.String)
	 */
	public ResetPasswordResult resetPassword(final String userId)
			throws DataAccessException {
		return (ResetPasswordResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.resetPassword(userId);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#retrieve(java.lang.String,
	 *      java.lang.String, java.lang.String[])
	 */
	public SObjectDynaBean[] retrieve(final String fields, final String type, final String[] ids)
			throws DataAccessException {
		return (SObjectDynaBean[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.retrieve(fields,type,ids);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#search(java.lang.String)
	 */
	public SearchResult search(final String sosl) throws DataAccessException {
		return (SearchResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.search(sosl);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#sendEmail(com.sforce.soap.partner.Email[])
	 */
	public SendEmailResult[] sendEmail(final Email[] emails) throws DataAccessException {
		return (SendEmailResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.sendEmail(emails);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#setPassword(java.lang.String,
	 *      java.lang.String)
	 */
	public SetPasswordResult setPassword(final String userId, final String password)
			throws DataAccessException {
		return (SetPasswordResult)execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.setPassword(userId,password);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#undelete(java.lang.String[])
	 */
	public UndeleteResult[] undelete(final String[] ids) throws DataAccessException {
		return (UndeleteResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.undelete(ids);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#update(com.sforce.soap.partner.sobject.SObject[])
	 */
	public SaveResult[] update(final SObjectDynaBean[] objects) throws DataAccessException {
		return (SaveResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.update(objects);
			}
		});
	}

	/**
	 * @see com.sforce.soap.partner.SoapBindingStub#upsert(java.lang.String,
	 *      com.sforce.soap.partner.sobject.SObject[])
	 */
	public UpsertResult[] upsert(final String externalIdField, final SObjectDynaBean[] objects)
			throws DataAccessException {
		return (UpsertResult[])execute(new ForceCallback() {
			public Object doInForce(ForceSession session) throws RemoteException, ApiFault {
				return session.upsert(externalIdField, objects);
			}
		});
	}
}
